import { createMarquee } from '@/scripts/components/marquee';
import { DIR, viewSize } from '@/scripts/constant';
import extentDOM from '@/scripts/helpers/extentDOM';
import { debounceBuilder } from '@/scripts/helpers/util';
import { upNearest, downNearest, leftNearest, rightNearest } from '@/scripts/math';

/** @type {import('node_modules/vue/types/options').ComponentOptions} */
const component = {
  name: 'SnelArea',
  props: {
    // 渲染标签
    tag: { type: String, default: 'div' },
    // 配置
    config: { type: Object, default: () => { return {}; } }
  },
  watch: {
    config: {
      handler() {
        const me = this;
        me.$setPagenation();
      },
      deep: true
    },
  },
  computed: {
    // 指定元素总数量
    total() {
      const me = this;
      return me.config.data?.length || me.doms.length;
    },
    // 当前元素
    curDom() {
      const me = this;
      return me.doms[me.currentIndex];
    },
    // 当前元素DOMRect数据
    curRect() {
      const me = this;
      return me.rects[me.currentIndex];
    },
  },
  data() {
    return {
      // 组件标识
      $__sign__: '__snel-area__',
      // 观察实例
      observer: null,
      // 抖动timer
      shakeTimer: null,
      // 页面控制器对象
      page: null,
      // 区域控制器id
      id: 0,
      // 当前索引
      currentIndex: 0,
      /** 暗焦点索引 */
      recordIndex: 0,
      // 当前容器元素
      container: null,
      // 当前容器元素DOMRect数据
      containerRect: null,
      // 移动锁
      moveLock: false,
      // 当前滑动容器元素一般即为container
      slideContainer: null,
      // 容器滑动量
      translate: null,
      // 容器最大滑动量
      maxTranslate: null,
      // 焦点dom元素集合
      doms: [],
      // 所有元素DOMRect数据集合(如指定了slide则还包含元素的滑动值translate)
      rects: [],
      // 文字滚动实例
      marquee: null,
      // 当前页数
      curPage: 1,
      // 总页数
      totalPage: 1,
      // 当前触发按键方向
      direction: null,
    };
  },
  render(h) {
    return h(this.tag, this.$slots.default);
  },
  mounted() {
    const me = this;
    me.container = me.$el;
    me.containerRect = me.$getBoundingClientRect(me.container);
    if (!me.slideContainer) {
      const containerId = me.config.slide?.containerId;
      me.slideContainer = extentDOM(containerId ? document.getElementById(containerId) : me.container);
    }
    // 初始化
    me.$init();
    // dom变更观察
    me.$MutationObserve();
    // 开发环境补充事件
    if (process.env.NODE_ENV === 'development') {
      me.$el.addEventListener('click', me.$click); // 单击
      me.$el.addEventListener('dblclick', me.$dblclick); // 双击
    }
  },
  // 释放资源
  destroyed() {
    const me = this;
    if (me.observer) {
      me.observer.disconnect();// 结束观察
      me.observer = null;
    }
    // 开发环境补充事件
    if (process.env.NODE_ENV === 'development') {
      me.$el.removeEventListener('click', me.$click); // 单击
      me.$el.removeEventListener('dblclick', me.$dblclick); // 双击
    }
  },
  /** @type {EpgAreaVueMethod} */
  methods: {
    // 注入计算方法
    upNearest, downNearest, leftNearest, rightNearest,
    $click(e) {
      const me = this;
      /** @type {EpgPageVueMethod} */
      const page = me.page;
      const index = me.doms.findIndex((d) => d.contains(e.target));
      if (index !== -1) {
        page?.focusArea?.setRecord();
        if (page?.focusArea?.id !== me.id || me.currentIndex !== index) {
          me.direction = index > me.currentIndex ? DIR.RIGHT : DIR.LEFT;
          page?.changeFocus(me.id, index);
        }
        me.clearSelect();// 上焦取消目标区域驻留
        page?.areas.forEach(area => { area.id !== me.id && area.setSelect(); });
      } else me.marqueeStop();
    },
    $dblclick(e) {
      const me = this;
      const index = me.doms.findIndex((d) => d.contains(e.target));
      if (index !== -1) me.$wrap('keyOk');
    },
    $wrap(name, ...args) {
      const me = this;
      const fn = me.config[name];
      return fn && fn(me, ...args);
    },
    $getBoundingClientRect(dom, add) {
      const me = this;
      let { x, y, left, right, top: $top, bottom, width, height } = dom.getBoundingClientRect();
      if (add) $top = me.containerRect.top + dom.offsetTop;
      width = dom.scrollWidth;
      height = dom.scrollHeight;
      right = left + width;
      bottom = $top + height;
      return { x, y, left, right, top: $top, bottom, width, height };
    },
    $setPagenation() {
      const me = this;
      if (me.config.paging) {
        let size = me.config.paging.size;
        if (!size || size > me.total) size = me.total;
        let total = me.config.paging.total;
        if (!total || total < me.total) total = me.total;
        if (total && size) me.totalPage = Math.ceil(total / size);
        if (size) me.curPage = Math.floor(me.currentIndex / size) + 1;
      }
    },
    $slidePrepare() {
      const me = this;
      const slide = me.config.slide;
      if (slide) {
        const mode = slide.mode || 'fixed';
        if (mode === 'fixed') {
          const dir = slide.slideDir || 'v';
          const h = dir === 'h';
          const startIndex = slide.startIndex;// 起始索引
          const maxWidth = viewSize.width;
          const maxHeight = viewSize.height;
          let offset = slide.offset || 0;// 末端偏移值
          if (!offset) {
            for (let i = 0; i < me.rects.length; i++) {
              const rect = me.rects[i];
              const origin = h ? rect.right : rect.bottom;
              const max = h ? maxWidth : maxHeight;
              if (origin > max) {
                const t = me.rects[i - 1];
                const origin = h ? t.right : t.bottom;
                offset = Math.round(max - origin);
                break;
              }
            }
          }
          const distance = [], values = [];
          me.rects.forEach((rect, i) => {
            let value = 0;
            const origin = h ? rect.right : rect.bottom;
            const max = h ? maxWidth : maxHeight;
            if (origin > max) {
              value = Math.round(max - offset - origin);
              values.push(value);
              distance.push(value);
            } else distance.push(value);
            me.rects[i].translate = value;
          });
          if (startIndex) {// 按起始索引计算
            const last = distance.slice(-1)[0];
            me.maxTranslate = last;
            distance.forEach((_, i) => {
              if (i >= startIndex) {
                const value = values[i - startIndex];
                me.rects[i].translate = Math.round(value ? value : last);
              } else me.rects[i].translate = 0;
            });
          }
        }
      }
    },
    $MutationObserve() {
      const me = this;
      /** @type {EpgPageVueMethod} */
      const page = me.page;
      /** @type {MutationObserver} */
      // eslint-disable-next-line no-undef
      const Mutation = MutationObserver ? MutationObserver : WebKitMutationObserver;
      if (Mutation) {
        let addDoms = [], removeDoms = [];// 变更的dom集合
        const domDebounce = debounceBuilder(() => {
          removeDoms.forEach(dom => {// 删除
            const index = me.doms.indexOf(dom);
            me.doms.splice(index, 1);
            me.rects.splice(index, 1);
          });
          addDoms.forEach(dom => {// 添加
            // dom.tabIndex = -1;
            me.doms.push(dom);
            me.rects.push(me.$getBoundingClientRect(dom, true));
          });
          addDoms.length = 0;// 重置
          removeDoms.length = 0;// 重置
          me.$slidePrepare();
          // 自动计算rowCount
          if (me.config.colCount) me.config.rowCount = Math.ceil(me.total / +me.config.colCount);
          if (page?.focusArea === me) {// 焦点在当前区域
            if (me.total) {
              if (me.currentIndex > me.total - 1) --me.currentIndex;// 越界前移
              page.changeFocus(me.id, me.currentIndex);
            } else {
              const target = me.id - 1;
              page.changeFocus(target >= 0 ? target : 0);// 区域清空后移
            }
          } else {// 焦点不在当前区域
            me.clearRecord();
            me.currentIndex = 0;
            me.$containerSlide(0);
          }
        }, 20);
        /** @type {MutationCallback} */
        const callkeyBack = (mutations) => {
          mutations.forEach(mutation => {
            // 检测dom变化
            if (mutation.type === 'childList' && mutation.target === me.container) {
              if (mutation.removedNodes.length) {// 删除元素
                const dom = mutation.removedNodes[0];
                if (dom?.classList.contains(me.config.itemClass || 'item') && me.doms.includes(dom)) {
                  removeDoms.push(dom);
                  domDebounce();
                }
              } else if (mutation.addedNodes.length) {// 添加元素
                const dom = mutation.addedNodes[0];
                if (dom?.classList.contains(me.config.itemClass || 'item') && !me.doms.includes(dom)) {
                  addDoms.push(dom);
                  domDebounce();
                }
              }
            }
          });
        };
        me.observer = new Mutation(callkeyBack);
        me.observer.observe(me.container, { childList: true, subtree: true });
      }
    },
    $border(direction) {
      const me = this;
      /** @type {EpgPageVueMethod} */
      const page = me.page;
      const bannedShake = me.config.bannedShake;
      const pBorderShake = page?.config.borderShake;
      if (me.shakeTimer) window.clearTimeout(me.shakeTimer);
      if (!bannedShake && pBorderShake) {
        const { interval = 1000, banned = [], className: name } = pBorderShake;
        const className = name || `shake-item-${direction}`;
        if (!banned.includes(me.id)) {
          const dom = me.curDom;
          if (!dom.className.includes(name || 'shake-item-')) {
            dom.classList.add(className);
            me.shakeTimer = window.setTimeout(() => {
              window.clearTimeout(me.shakeTimer);
              dom.classList.remove(className);
            }, interval);
            return;
          }
        }
      }
      const borderName = `border${direction.replace(/( |^[a-z])/g, (l) => l.toUpperCase())}`;
      me.$wrap(borderName);
      page?.$wrap(page, 'borderListener', direction);
    },
    $crossArea(direction, dirIndex, step) {
      const me = this;
      /** @type {EpgPageVueMethod} */
      const page = me.page;
      const total = page?.total || 0;
      const areas = page?.areas || [];
      // 传入的区域id
      const dir = me.config.outMoveDir?.[dirIndex];
      // 对应方向的区域id
      let dirTo = me.id;
      // 默认的目标区域id
      const target = me.id + step;
      if (target < 0 || target >= total) {
        me.$border(direction);
        return;
      }
      const dirMethod = me[`${direction}Nearest`];
      // 单区域情况
      if (typeof dir === 'number') dirTo = dir;
      // 多区域情况
      else if (Array.isArray(dir)) {
        // 获取容器定位信息
        const allRects = dir.map(current => areas[current]?.containerRect);
        if (me.total) {
          const areaId = dirMethod(me.curRect, allRects);
          dirTo = areaId !== -1 ? dir[areaId] : target;
        } else dirTo = dir[0];
      } else dirTo = target; // 未指定使用默认目标
      if (dirTo === me.id) {// 自身及触边
        me.$border(direction);
      } else {
        const target = areas[dirTo];
        if (target) {
          const h = page?.config.slide.slideDir === 'h';
          if (!target.total) {
            if (h) return;
            if (target.id === total - 1) return;
          }
          const rects = target.rects;
          let index = target.recordIndex;// 目标区域暗焦点
          if (!target.config.record) {
            if (me.total && target.total && target.config.slide?.slideDir !== 'h') {
              index = dirMethod(me.curRect, rects);
            } else index = 0;
          }
          if (index !== -1) {
            // 当前出区域事件
            me.$wrap('areaOut', dirTo, index);
            page?.changeFocus(dirTo, index);
            me.setRecord();// 设置暗焦点
            me.setSelect();// 设置驻留
            // 目标进区域事件
            target.$wrap('areaIn', me.id, me.currentIndex);
            target.clearSelect();// 上焦取消目标区域驻留
          } else me.$border(direction);
        } else me.$border(direction);
      }
    },
    $switchFocusByRowCol(direction, dirIndex, step) {
      const me = this;
      // 传入的区域id
      const dir = me.config.outMoveDir?.[dirIndex];
      const curRow = me.getCurrentRow();
      const curCol = me.getCurrentCol();
      const colCount = +me.config.colCount;
      const rowCount = me.config.rowCount = Math.ceil(me.total / colCount);
      let index = me.currentIndex;
      const lastIndex = me.total - 1;
      // 边界处理
      const outMove = undefined !== dir && dir !== -1;
      const leftBorder = direction === DIR.LEFT && outMove && curCol === 1;
      const rightBorder = direction === DIR.RIGHT && outMove && (curCol === colCount || index === lastIndex);
      const upBorder = direction === DIR.UP && outMove && curRow === 1;
      const downBorder = direction === DIR.DOWN && outMove && curRow === rowCount;
      if (leftBorder || rightBorder || upBorder || downBorder) {// 有跨界区域
        me.$crossArea(direction, dirIndex, step);
      } else {
        switch (direction) {
          case DIR.LEFT:
            if (me.config.cycleH) {
              if (curCol === 1) index += colCount - 1;
              else index -= 1;
            } else index -= 1;
            break;
          case DIR.RIGHT:
            if (me.config.cycleH) {
              if (curCol === colCount || index === lastIndex) {
                index = (curRow - 1) * colCount;
              } else index += 1;
            } else index += 1;
            break;
          case DIR.UP:
            if (me.config.cycleV && curRow === 1) {
              index += (rowCount - 1) * colCount;
              index = index > lastIndex ? index - colCount : index;
            } else if (curRow > 1) index -= colCount;
            break;
          case DIR.DOWN:
            if (me.config.cycleV) {
              if (curRow === rowCount || index + colCount > lastIndex) index %= colCount;
              else if (curRow < rowCount) index += colCount;
            } else if (curRow < rowCount) index += colCount;
            break;
          default: break;
        }
        index = index > lastIndex ? lastIndex : index;
        index = index < 0 ? 0 : index;
        index !== me.currentIndex ? me.page?.changeFocus(me.id, index) : me.$border(direction);
      }
    },
    $switchFocus(direction, dirIndex, step = 0) {
      const me = this;
      me.direction = direction;
      // 自定义方法
      const customName = `custom${direction.replace(/( |^[a-z])/g, (l) => l.toUpperCase())}`;
      // 存在自定义方法则执行
      const customMeth = me.config[customName];
      if (customMeth) {
        const custom = customMeth(me);
        if (!custom) return;
      }
      if (undefined !== me.config.colCount) {// 存在行列配置
        me.$switchFocusByRowCol(direction, dirIndex, step);
      } else {
        const dirMethod = me[`${direction}Nearest`];
        const index = me.total ? dirMethod(me.curRect, me.rects) : -1;
        if (index !== -1) {
          if (index === me.recordIndex) me.clearSelect();// 取消驻留
          else if (me.config.selectInside) me.switchSelect(me.recordIndex);// 内部切换驻留
          me.page?.changeFocus(me.id, index); // 内部焦点切换
        } else me.$crossArea(direction, dirIndex, step);
      }
    },
    $containerSlide(transition) {
      const me = this;
      const slide = me.config.slide;
      if (slide) {
        if (me.slideContainer) {
          const currentIndex = me.currentIndex;
          const dir = slide.slideDir || 'v';
          const mode = slide.mode || 'fixed';
          let distance = 0;
          if (mode === 'fixed') {
            distance = me.rects[currentIndex]?.translate;
          } else {
            const step = slide.step || 0;
            const startIndex = slide.startIndex || 1;
            const colCount = +me.config.colCount;
            const rowCount = me.config.rowCount = Math.ceil(me.total / colCount);
            const index = Math.floor(currentIndex / (dir === 'h' ? rowCount : colCount));
            if (index > startIndex) distance = -step * (index - 1);
          }
          if (me.translate !== distance) {
            me.translate = distance;
            const fn = () => {
              me.moveLock = false;
              me.slideContainer.removeEventListener('transitionend', fn);
            };
            me.slideContainer.addEventListener('transitionend', fn);
            // 滑动锁定
            if (!slide.slideFree) me.moveLock = true;
            const firstLoad = me.page?.firstLoad;
            const speed = undefined === firstLoad || firstLoad ? 0 : undefined !== transition ? transition : slide.transition;
            me.slideContainer.slide(distance, dir, speed);
          } else me.moveLock = false;
        }
      }
    },
    getDomList() {
      const me = this;
      const doms = me.container?.getElementsByClassName(me.config.itemClass || 'item') || [];
      me.doms = [...doms];
      return me.doms;
    },
    clearRecord() {
      const me = this;
      if (me.config.record) me.recordIndex = 0;
    },
    setRecord(index) {
      const me = this;
      if (me.config.record) me.recordIndex = index || me.currentIndex;
    },
    clearSelect() {
      const me = this;
      if (me.config.select) me.doms[me.recordIndex]?.classList.remove(me.config.selectClass || 'item_select');
    },
    setSelect() {
      const me = this;
      if (me.config.select) me.doms[me.recordIndex]?.classList.add(me.config.selectClass || 'item_select');
    },
    switchSelect(index) {
      const me = this;
      if (index < me.total) {
        me.clearSelect();
        me.setRecord(index);
        me.setSelect();
      }
    },
    getCurrentRow() {
      const me = this;
      if (!me.config.colCount) return 1;
      return Math.floor(me.currentIndex / +me.config.colCount) + 1;
    },
    getCurrentCol() {
      const me = this;
      if (!me.config.colCount) return 1;
      const col = Math.ceil((me.currentIndex + 1) % +me.config.colCount);
      return col === 0 ? +me.config.colCount : col;
    },
    marqueeMove() {
      const me = this;
      if (!me.marquee) {
        const item = me.curDom;
        const element = item?.getElementsByClassName(me.config.marqueeClass || 'txt')?.[0];
        const config = Object.assign({ element }, me.config.marqueeConfig);
        me.marquee = createMarquee(config);
      }
      me.marquee.move();
    },
    marqueeStop() {
      const me = this;
      if (me.marquee) {
        me.marquee.stop();
        me.marquee = null;
      }
    },
    resetSlide(transition) {
      const me = this;
      me.currentIndex = 0;
      me.$containerSlide(transition);
    },
    $moveUp() {
      const me = this;
      !me.moveLock && me.$switchFocus(DIR.UP, 0, -1);
    },
    $moveDown() {
      const me = this;
      !me.moveLock && me.$switchFocus(DIR.DOWN, 2, 1);
    },
    $moveLeft() {
      const me = this;
      !me.moveLock && me.$switchFocus(DIR.LEFT, 1);
    },
    $moveRight() {
      const me = this;
      !me.moveLock && me.$switchFocus(DIR.RIGHT, 3);
    },
    $init() {
      const me = this;
      me.doms = me.getDomList();
      me.rects = me.doms.map((dom) => me.$getBoundingClientRect(dom));
      me.$slidePrepare();
    },
  },
};

export default component;
